﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace AnaxibiaStudios.Smug.Internal
{
  public abstract class MemberInfoEx
  {
    public bool IsNull { get { return Access == Access.Null; } }
    public bool IsPublic { get { return Access == Access.Public; } }
    public bool IsProtected { get { return Access == Access.Protected; } }
    public bool IsPrivate { get { return Access == Access.Private; } }

    public bool IsSealed { get { return OverrideState == MethodOverrideState.Sealed; } }
    public bool IsExplicit { get { return OverrideState == MethodOverrideState.Explicit; } }
    public bool IsAbstract { get { return OverrideState == MethodOverrideState.Abstract; } }
    public bool IsVirtual { get { return OverrideState == MethodOverrideState.Virtual; } }

    public bool IsInternal { get; protected set; }

    public bool IsValidForDerivedClass { get { return !IsNull && !IsPrivate && (!IsSealed || MemberType == MemberTypes.Constructor) && !IsInternal; } }

    public void MakeExplicit() { OverrideState = MethodOverrideState.Explicit; }

    public MemberInfo MemberInfo { get; protected set; }

    public MemberTypes MemberType { get { return MemberInfo.MemberType; } }
    public Type DeclaringType { get { return MemberInfo.DeclaringType; } }
    public MemberCollection _parent;

    public string ExplicitPrefix { get; protected set; }
    public string Name { get; protected set; }

    public Type ReturnType { get; protected set; }
    public ParameterInfoEx[] Parameters { get; protected set; }

    protected MethodOverrideState OverrideState { get; set; }
    public BaseCall BaseCall { get; protected set; }
    protected Access Access { get; set; }

    public MemberInfoEx(MemberCollection parent, MemberInfo member)
    {
      _parent = parent;
      MemberInfo = member;
      Access = Access.Null;
      BaseCall = BaseCall.Null;

      ExplicitPrefix = member == null ? "[null]" : _parent.Builder.GetExplicitPrefix(MemberInfo, true);
      Name = member == null ? "[null]" : _parent.Builder.PlainMemberName(MemberInfo, true, true, true);
    }

    public static bool SharedAccess(MemberInfoEx a, MemberInfoEx b)
    {
      return a.Access == b.Access;
    }

    protected void SetModifiers(MethodBase method)
    {
      Access = ParseAccess(method);
      OverrideState = DetermineMethodOverrideState(method);
      IsInternal = DetermineInternal(method);

      if (method.IsAbstract || method.DeclaringType.IsInterface)
        BaseCall = BaseCall.Null;
      else if (method.IsPrivate && method.IsVirtual)
        BaseCall = BaseCall.Explicit;
      else
        BaseCall = BaseCall.Default;
    }

    protected void SetParameters(Type retType, ParameterInfo[] parameters)
    {
      ReturnType = retType;

      Parameters = parameters
        .Select(pi => new ParameterInfoEx
        {
          Name = pi.Name,
          PassBy = pi.IsOut ? ParameterPassBy.Out : pi.ParameterType.IsByRef ? ParameterPassBy.Ref : ParameterPassBy.Standard,
          Type = pi.ParameterType
        }).ToArray();
    }

    public static MethodInfoEx Wrap(MemberCollection parent, MethodInfo method)
    {
      return Wrap(parent, method as MemberInfo) as MethodInfoEx;
    }
    public static MethodInfoEx SoloWrap(SourceBuilder builder, MethodInfo method)
    {
      var parent = new MemberCollection(builder);
      return Wrap(parent, method);
    }

    public static PropertyInfoEx Wrap(MemberCollection parent, PropertyInfo property)
    {
      return Wrap(parent, property as MemberInfo) as PropertyInfoEx;
    }
    public static PropertyInfoEx SoloWrap(SourceBuilder builder, PropertyInfo method)
    {
      var parent = new MemberCollection(builder);
      return Wrap(parent, method);
    }

    public static EventInfoEx Wrap(MemberCollection parent, EventInfo ev)
    {
      return Wrap(parent, ev as MemberInfo) as EventInfoEx;
    }
    public static EventInfoEx SoloWrap(SourceBuilder builder, EventInfo method)
    {
      var parent = new MemberCollection(builder);
      return Wrap(parent, method);
    }

    public static ConstructorInfoEx Wrap(MemberCollection parent, ConstructorInfo ev)
    {
      return Wrap(parent, ev as MemberInfo) as ConstructorInfoEx;
    }
    public static ConstructorInfoEx SoloWrap(SourceBuilder builder, ConstructorInfo method)
    {
      var parent = new MemberCollection(builder);
      return Wrap(parent, method);
    }

    public static MemberInfoEx Wrap(MemberCollection parent, MemberInfo member)
    {
      if (member == null)
        return new NullMemberInfoEx(parent);

      switch (member.MemberType)
      {
        case MemberTypes.Method:
          return new MethodInfoEx(parent, member as MethodInfo);
        case MemberTypes.Property:
          return new PropertyInfoEx(parent, member as PropertyInfo);
        case MemberTypes.Event:
          return new EventInfoEx(parent, member as EventInfo);
        case MemberTypes.Constructor:
          return new ConstructorInfoEx(parent, member as ConstructorInfo);
      }

      return new NullMemberInfoEx(parent);
    }
    public static MemberInfoEx SoloWrap(SourceBuilder builder, MemberInfo member)
    {
      var parent = new MemberCollection(builder);
      return Wrap(parent, member);
    }

    protected static MemberInfoEx SelectMorePublic(MemberInfoEx memA, MemberInfoEx memB)
    {
      return IsMorePublic(memA.Access, memB.Access) ? memA : memB;
    }

    protected static bool IsMorePublic(Access accessA, Access accessB)
    {
      return
        accessA == Access.Null ? false :
        accessB == Access.Null ? true :
        accessA == Access.Public ? true :
        accessB == Access.Public ? false :
        accessA == Access.Protected ? true :
        accessB == Access.Protected ? false : true;
    }

    protected static Access ParseAccess(MethodBase method)
    {
      return
        method == null ? Access.Null :
        method.IsPublic ? Access.Public : method.IsPrivate ? Access.Private : Access.Protected;
    }

    public static MethodOverrideState DetermineMethodOverrideState(MethodBase method)
    {
      if (method.IsVirtual && method.IsPrivate)
        return MethodOverrideState.Explicit;

      if (method.DeclaringType.IsInterface)
        return MethodOverrideState.Virtual;

      if (method.IsAbstract)
        return MethodOverrideState.Abstract;

      if (!method.IsVirtual || method.IsFinal)
        return MethodOverrideState.Sealed;

      return MethodOverrideState.Virtual;
    }

    public static bool DetermineInternal(MethodBase method)
    {
      var atts = method.Attributes;

      if
      (
        HasMethodAttribute(atts, MethodAttributes.Private) &&
        HasMethodAttribute(atts, MethodAttributes.FamANDAssem) &&

        !HasMethodAttribute(atts, MethodAttributes.Family) &&
        !HasMethodAttribute(atts, MethodAttributes.FamORAssem) &&
        !HasMethodAttribute(atts, MethodAttributes.Public)
      )
        return true;

      return false;
    }

    protected static bool HasMethodAttribute(MethodAttributes set, MethodAttributes key)
    {
      return (set & key) == key;
    }

    public override string ToString()
    {
      return MemberInfo.ToString();
    }

    private bool MatchesNonOverloadBase(MemberInfoEx that)
    {
      if (ReturnType != that.ReturnType || !SharedAccess(this, that))
        return false;

      return MatchesNonOverload(that); 
    }
    protected abstract bool MatchesNonOverload(MemberInfoEx that);
    protected abstract bool IsOverloadable();

    public virtual PreexistingType Hit(MemberInfoEx that)
    {
      var options = new SignatureOptions
      {
        IgnoreParamNames = true,
        MethodName = string.Empty,
        IgnoreReturn = true,
        IgnoreAccess = true,
        OverrideText = string.Empty,
        RefSubstitute = "ByRef",
      };

      string thisSig = BuildSignature(options);
      string thatSig = that.BuildSignature(options);
      bool overloading = thisSig != thatSig;
      bool sameNonOverload = MatchesNonOverloadBase(that);
      bool sharedGroup = (MemberType == that.MemberType) && (this.IsOverloadable() && that.IsOverloadable());

      if (this.Name != that.Name)
        return PreexistingType.New;
      else if (overloading && sharedGroup)
        return PreexistingType.New;
      else if (!overloading && sameNonOverload)
        return PreexistingType.Exact;
      else
        return PreexistingType.Conflicting;
    }

    public abstract string BuildBody();

    public string BuildSignature()
    {
      return BuildSignature(new SignatureOptions());
    }

    public string BuildSignature(SignatureOptions options)
    {
      var builder = _parent.Builder;
      string source = "";

      bool useExplicit = options.UseExplicit || IsExplicit;

      bool ignoreAccess = useExplicit ? true : options.IgnoreAccess;
      string overrideText = useExplicit ? string.Empty : options.OverrideText;

      string methodName = options.MethodName ?? ((useExplicit ? ExplicitPrefix : string.Empty) + Name + BuildTemplate());

      source += ignoreAccess ? string.Empty : BuildAccess();
      source += overrideText ?? BuildOverride();
      source += SourceBuilder.Space;
      source += options.IgnoreReturn ? string.Empty : (SourceBuilder.IsVoid(ReturnType) ? SourceBuilder.VoidName : builder.BuildTypeName(ReturnType));
      source += SourceBuilder.Space;
      source += methodName;

      if (!options.IgnoreParamTypes || !options.IgnoreParamNames)
      {
        source += ArgumentScope().Add(
          BuildParams(new ParameterOptions 
                      { 
                        RefSubstitute = options.RefSubstitute, 
                        IgnoreTypes = options.IgnoreParamTypes, 
                        IgnoreNames = options.IgnoreParamNames, 
                        NamePrefix = options.ParamPrefix 
                      }));
      }

      return source;
    }

    protected virtual Scope ArgumentScope()
    {
      return new Scope(Scope.WrapTypeEnum.Parenthesis);
    }

    protected virtual string BuildTemplate()
    {
      return string.Empty;
    }

    public virtual string BuildAccess()
    {
      return BuildAccess(null);
    }

    protected virtual string BuildOverride()
    {
      if (!MemberInfo.DeclaringType.IsInterface)
      {
        return SourceBuilder.Override;
      }

      return SourceBuilder.Virtual;
    }
    
    public virtual string BuildAccess(PropertyInfoEx prop)
    {
      if (prop != null && SharedAccess(this, prop))
      {
        return string.Empty;
      }

      string sp = SourceBuilder.Space;
      string access = string.Empty;

      if (IsPublic)
        access = SourceBuilder.Public;
      else if (IsProtected)
        access = SourceBuilder.Protected;
      else
        sp = string.Empty; // explicit

      return access + sp;
    }

    public virtual string BuildSource()
    {
      string source = "";

      source += BuildSignature();
      source += BuildBody();

      return source;
    }

    public virtual string BuildParams(ParameterOptions options)
    {
      string source = string.Empty;
      bool first = true;

      foreach (var parm in Parameters)
      {
        if (!first)
        {
          source += SourceBuilder.Comma + SourceBuilder.Space;
        }
        first = false;

        string refSrc =
          parm.PassBy == ParameterPassBy.Ref ? SourceBuilder.Ref :
          parm.PassBy == ParameterPassBy.Out ? SourceBuilder.Out :
          string.Empty;
        source += refSrc.Length > 0 ? (options.RefSubstitute ?? refSrc) : string.Empty;
        source += parm.PassBy != ParameterPassBy.Standard ? SourceBuilder.Space : string.Empty;

        if (!options.IgnoreTypes)
        {
          source += _parent.Builder.BuildTypeName(parm.Type);
          source += SourceBuilder.Space;
        }

        if (!options.IgnoreNames)
        {
          source += options.NamePrefix ?? string.Empty;
          source += parm.Name + SourceBuilder.Space;
        }
      }

      return source;
    }
  }

  public abstract class MemberInfoEx<TMember> : MemberInfoEx where TMember : MemberInfo
  {
    public TMember Info { get { return this.MemberInfo as TMember; } }

    public MemberInfoEx(MemberCollection parent, TMember mem)
      : base(parent, mem)
    {
    }
  }
}
